#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <cassert>
#include <cstdio>
#include <mysql/mysql.h>

#include "../common/asynclog.hpp"

namespace yx
{
    namespace model_file
    {
        struct Question
        {
        public:
            std::string _number;     // 题目编号
            std::string _title;      // 题目标题
            std::string _difficulty; // 难度等级
            int _cpulimit;           // CPU运行时间限制S
            int _memlimit;           // 内存限制KB
            std::string _desc;       // 题目描述
            std::string _head;       // 题目预设给用户在线编辑器的代码
            std::string _tail;       // 题目的测试用例，需要和header拼接，形成完整代码

            void show()
            {
                printf("_number: %s\n", _number.c_str());
                printf("_title: %s\n", _title.c_str());
                printf("_difficulty: %s\n", _difficulty.c_str());
                printf("_cpulimit: %d\n", _cpulimit);
                printf("_memlimit: %d\n", _memlimit);
                printf("_desc: %s\n", _desc.c_str());
                printf("_head: %s\n", _head.c_str());
                printf("_tail: %s\n", _tail.c_str());
            }

            bool operator>(const struct Question &q) const
            {
                return _number > q._number;
            }

            bool operator<(const struct Question &q) const
            {
                return _number < q._number;
            }

            bool operator>=(const struct Question &q) const
            {
                return _number >= q._number;
            }

            bool operator<=(const struct Question &q) const
            {
                return _number <= q._number;
            }
        };

        static const std::string questions_list_path = "./questions/questions.list";
        static const std::string question_path = "./questions/";
        static const std::string desc_file = "/desc.txt";
        static const std::string head_file = "/head.cpp";
        static const std::string tail_file = "/tail.cpp";
        static const int STR_COUNT = 5;

        class Model
        {
        private:
            std::unordered_map<std::string, struct Question> _questions; // 题目编号及题目详细信息的映射

        public:
            Model()
            {
                assert(loadAllQuestions(questions_list_path));
            }
            ~Model()
            {
            }

            bool loadAllQuestions(const std::string &config_file)
            {
                // 加载配置文件: ./questions/questions.list
                std::ifstream fin(config_file);
                if (!fin.is_open())
                {
                    (FLOG()("打开配置文件 {} 失败!!!", questions_list_path));
                    return false;
                }

                std::string line;
                // 一行行的读取配置文件
                while (std::getline(fin, line))
                {
                    std::vector<std::string> tokens;
                    int count = StringUtil::cutString(line, " ", tokens);
                    if (count != STR_COUNT)
                    {
                        (WLOG()("加载某个题目失败, line = {}!!!", line));
                        continue;
                    }
                    // 构建题目
                    struct Question q;
                    q._number = tokens[0];
                    q._title = tokens[1];
                    q._difficulty = tokens[2];
                    q._cpulimit = atoi(tokens[3].c_str());
                    q._memlimit = atoi(tokens[4].c_str());

                    // 读取题目内容
                    if (!FileUtil::readFile(question_path + q._number + desc_file, q._desc))
                    {
                        (WLOG()("读取文件 {} 失败!!!", question_path + q._number + desc_file));
                    }
                    FileUtil::readFile(question_path + q._number + head_file, q._head);
                    FileUtil::readFile(question_path + q._number + tail_file, q._tail);

                    // 保存到hash中
                    _questions.insert(make_pair(q._number, q));
                }

                (ILOG()("通过配置文件 {} 加载题库成功!!!", questions_list_path));
                return true;
            }

            // 获取所有题目信息
            bool getAllQuestions(std::vector<struct Question> &all)
            {
                // std::unordered_map<std::string,struct Question>::iterator it=_questions.begin();
                if (_questions.empty())
                {
                    (WLOG()("用户获取题目列表失败!!!"));
                    return false;
                }

                for (auto it = _questions.begin(); it != _questions.end(); ++it)
                {
                    all.push_back(it->second);
                }

                return true;
            }

            // 根据题目编号获取指定题目信息
            bool getOneQuestion(const std::string &number, Question &q)
            {
                auto iter = _questions.find(number);
                if (iter == _questions.end())
                {
                    (WLOG()("用户获取 number = {} 题目信息失败!!!", number));
                    return false;
                }

                q = iter->second;

                return true;
            }

            void showOneQuestion(const std::string &number)
            {
                auto iter = _questions.find(number);
                if (iter != _questions.end())
                {
                    iter->second.show();
                }
            }
        };
    }

    inline namespace model_mysql
    {
        struct Question
        {
        public:
            std::string _number;     // 题目编号
            std::string _title;      // 题目标题
            std::string _difficulty; // 难度等级
            int _cpulimit;           // CPU运行时间限制S
            int _memlimit;           // 内存限制KB
            std::string _desc;       // 题目描述
            std::string _head;       // 题目预设给用户在线编辑器的代码
            std::string _tail;       // 题目的测试用例，需要和header拼接，形成完整代码

            void show()
            {
                printf("_number: %s\n", _number.c_str());
                printf("_title: %s\n", _title.c_str());
                printf("_difficulty: %s\n", _difficulty.c_str());
                printf("_cpulimit: %d\n", _cpulimit);
                printf("_memlimit: %d\n", _memlimit);
                printf("_desc: %s\n", _desc.c_str());
                printf("_head: %s\n", _head.c_str());
                printf("_tail: %s\n", _tail.c_str());
            }

            bool operator>(const struct Question &q) const
            {
                return _number > q._number;
            }

            bool operator<(const struct Question &q) const
            {
                return _number < q._number;
            }

            bool operator>=(const struct Question &q) const
            {
                return _number >= q._number;
            }

            bool operator<=(const struct Question &q) const
            {
                return _number <= q._number;
            }
        };

        const std::string table = "questions";
        const std::string host = "127.0.0.1";
        const std::string user = "oj_client";
        const std::string passwd = "123456789Yx...";
        const std::string db = "oj";
        const int port = 3306;

        class Model
        {
        private:
        public:
            Model()
            {
            }
            ~Model()
            {
            }

            bool queryMySQL(const std::string &sql, std::vector<Question> &out)
            {
                // 创建mysql句柄
                MYSQL *my = mysql_init(nullptr);

                // 连接数据库
                if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0))
                {
                    (FLOG()("连接数据库失败!!!")); 
                    return false;
                }

                // 一定要设置该链接的编码格式, 要不然会出现乱码问题
                mysql_set_character_set(my, "utf8");

                (ILOG()("连接数据库成功, 要执行SQL= {}",sql)); 

                // 执行sql语句
                if (0 != mysql_query(my, sql.c_str()))
                {
                    (WLOG()("sql= {} 执行失败!!!",sql));
                    return false;
                }

                // 提取结果
                MYSQL_RES *res = mysql_store_result(my);

                // 分析结果
                int rows = mysql_num_rows(res);   // 获得行数量
                int cols = mysql_num_fields(res); // 获得列数量

                Question q;

                for (int i = 0; i < rows; i++)
                {
                    MYSQL_ROW row = mysql_fetch_row(res);
                    q._number = row[0];
                    q._title = row[1];
                    q._difficulty = row[2];
                    q._desc = row[3];
                    q._head = row[4];
                    q._tail = row[5];
                    q._cpulimit = atoi(row[6]);
                    q._memlimit = atoi(row[7]);

                    out.push_back(q);
                }
                // 释放结果空间
                //free(res);
                // 关闭mysql连接
                mysql_close(my);

                return true;
            }

            // 获取所有题目信息
            bool getAllQuestions(std::vector<struct Question> &all)
            {
                std::string sql = "select * from ";
                sql += table;

                if (!queryMySQL(sql, all))
                {
                    (WLOG()("用户获取题目列表失败!!!"));
                    return false;
                }

                return true;
            }

            // 根据题目编号获取指定题目信息
            bool getOneQuestion(const std::string &number, Question &q)
            {
                std::string sql = "select * from ";
                sql += table;
                sql += " where number=";
                sql += number;

                std::vector<struct Question> one;
                if (!queryMySQL(sql, one) && one.size() == 1)
                {
                    (WLOG()("用户获取 number = {} 题目信息失败!!!", number));
                    return false;
                }
                q = one.front();
                return true;
            }

            void showOneQuestion(const std::string &number)
            {
                struct Question q;
                if(getOneQuestion(number,q))
                {
                    q.show();
                }
            }
        };
    }
}